/**
 * 	 Copyright (c) 2008, 2009 Lukas Zaruba
 * 
 *   This file is part of UniAnalyzer (Universal Analyzer).
 *
 *   UniAnalyzer is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   UniAnalyzer is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with UniAnalyzer. If not, see <http://www.gnu.org/licenses/>.
 */

package net.zarubsys.unianalyzer.dataviews.lpt.gui.graph.editor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import net.zarubsys.unianalyzer.dataminers.lpt.pin.CustomizedPin;
import net.zarubsys.unianalyzer.dataminers.lpt.results.IResult;
import net.zarubsys.unianalyzer.dataminers.lpt.triggers.TriggerDescription;
import net.zarubsys.unianalyzer.dataviews.lpt.gui.graph.preferences.PreferenceConstants;
import net.zarubsys.unianalyzer.dataviews.lpt.gui.graph.provider.MetadataProvider;
import net.zarubsys.unianalyzer.dataviews.lpt.gui.graph.utils.GraphEditorPlugin;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IPathEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;

/**
 * GraphEditor
 *
 * @author  &lt;A HREF=&quot;mailto:lukas.zaruba@media-solutions.cz&quot;&gt;Lukas Zaruba&lt;/A&gt;, MEDIA SOLUTIONS CZECH REPUBLIC Ltd.
 * @version $Revision$ $Date$
 */
public class GraphEditor extends EditorPart {
	
	private static final String VALUE_CHANGE_TOOLTIP = "Click right button into the graph to change time units or value style";

	private static final int NAVIGATOR_COL_WIDTH = 30;

	private static final int VERTICAL_OFFSET = 105;

	IResult result;
	
	private Text realFreq;
	private Label triggerPinDescription;
	private Label triggerEdgeDescription;
	private Text date;
	private Text address;
	private Text desiredFreq;
	
	private int cursor1Position = -1;
	private int cursor2Position = -1;
	
	private int samplesPerPage;
	private int samplesOffset = 0;
	
	private int jumpSize;
	private int minZoom;
	private int maxZoom;
	
	private DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.getDefault());

	private Canvas canvas;

	private Text c2Value;
	private Text statisticsDistance;
	private Text c2Position;
	private Text c1Value;
	private Text c1Position;
	private Label dataSummary;

	private Label currentPositionDescription;

	private Button goLast;
	private Button goNext;
	private Button goPrevious;
	private Button goFirst;
	private Button zoomOut;
	private Button zoomFit;
	private Button zoomIn;

	private Button goJump;

	private MenuItem timeUnitsS;
	private MenuItem timeUnitsMs;
	private MenuItem timeUnitsNs;
	private MenuItem valueFormsDec;
	private MenuItem valueFormsHex;
	private MenuItem valueFormsBin;
	
	private MetadataProvider metadataProvider; 
	private int valueStyle;
	private int timeStyle;

	private Menu styleMenu;
	
	private int currentCursorColumn;

	private Button discardCursors;

	private Canvas navigationCanvas;
	
	private static final Logger log = Logger.getLogger(GraphEditor.class);

	
	public void doSave(IProgressMonitor monitor) {
	}

	
	public void doSaveAs() {
	}
	
	public void init(IEditorSite site, IEditorInput input) throws PartInitException {
		setSite(site);
		if (!(input instanceof IPathEditorInput)) {
			throw new PartInitException("Invalid input: Must be IPathEditorInput");
		} 
		setInput(input);
		
		IPathEditorInput pathInput = (IPathEditorInput) input;
		result = getResult(pathInput);
		setPartName(pathInput.getName());
		
	}
	
	private IResult getResult(IPathEditorInput pathInput) throws PartInitException {
		File file = pathInput.getPath().toFile();
		ObjectInputStream ois = null;
		IResult result = null;
		try {
			ois = new ObjectInputStream(new FileInputStream(file));
			result = (IResult) ois.readObject();
		} catch (FileNotFoundException e) {
			log.error("Error reading measuring result.", e);
			throw new PartInitException("Error reading measuring result.", e);
		} catch (IOException e) {
			log.error("Error reading measuring result.", e);
			throw new PartInitException("Error reading measuring result.", e);
		} catch (ClassNotFoundException e) {
			log.error("Error reading measuring result.", e);
			throw new PartInitException("Error reading measuring result.", e);
		} finally {
			if (ois != null) {
				try {
					ois.close();
				} catch (IOException e) {
					log.error("Error closing measuring result input stream.", e);
					throw new PartInitException("Error closing measuring result input stream.", e);
				}
			}
		}
		return result;
	}

	
	public boolean isDirty() {
		return false;
	}

	
	public boolean isSaveAsAllowed() {
		return false;
	}

	
	public void createPartControl(Composite parent) {
		Label label;
		GridData gd;
		
		final Composite composite = new Composite(parent, SWT.NONE);
		composite.setLayout(new GridLayout(1, false));
		
		// top
		Composite metaData = new Composite(composite, SWT.NONE);
		metaData.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		metaData.setLayout(new GridLayout(1, false));
		
		Group metadataGroup = new Group(metaData, SWT.NONE);
		metadataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		metadataGroup.setLayout(new GridLayout(3, false));
		metadataGroup.setText("Metadata");
		
		// top left
		Composite metaDataLeft = new Composite(metadataGroup, SWT.NONE);
		metaDataLeft.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, true));
		metaDataLeft.setLayout(new GridLayout(2, false));
		
		label = new Label(metaDataLeft, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Date:");
		
		date = new Text(metaDataLeft, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		date.setEnabled(false);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 120;
		date.setLayoutData(gd);
		
		label = new Label(metaDataLeft, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Address:");
		
		address = new Text(metaDataLeft, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 120;
		address.setLayoutData(gd);
		address.setEnabled(false);
		
		// top middle
		Composite metaDataMiddle = new Composite(metadataGroup, SWT.NONE);
		metaDataMiddle.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, true));
		metaDataMiddle.setLayout(new GridLayout(2, false));
		
		label = new Label(metaDataMiddle, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Desired freq.:");
		
		desiredFreq = new Text(metaDataMiddle, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 100;
		desiredFreq.setLayoutData(gd);
		desiredFreq.setEnabled(false);
		
		label = new Label(metaDataMiddle, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Real freq.:");
		
		realFreq = new Text(metaDataMiddle, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 100;
		realFreq.setLayoutData(gd);
		realFreq.setEnabled(false);
		
		// top right
		Composite metaDataRight = new Composite(metadataGroup, SWT.NONE);
		metaDataRight.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, true));
		metaDataRight.setLayout(new GridLayout(1, false));
		
		label = new Label(metaDataRight, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Trigger:");
		
		triggerPinDescription = new Label(metaDataRight, SWT.NONE);
		gd = new GridData(SWT.BEGINNING, SWT.BOTTOM, false, false);
		label.setLayoutData(gd);
		
		triggerEdgeDescription = new Label(metaDataRight, SWT.NONE);
		triggerEdgeDescription.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		
		// middle
		Composite data = new Composite(composite, SWT.NONE);
		data.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		data.setLayout(new GridLayout(1, true));
		
		createPopupMenu(composite);
		
		Group dataGroup = new Group(data, SWT.NONE);
		dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		dataGroup.setLayout(new GridLayout(1, false));
		dataGroup.setText("Data");
		
		Composite dataDescription = new Composite(dataGroup, SWT.NONE);
		dataDescription.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		dataDescription.setLayout(new GridLayout(1, false));
		
		dataSummary = new Label(dataDescription, SWT.NONE);
		dataSummary.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
		dataSummary.setToolTipText(VALUE_CHANGE_TOOLTIP);
		
		currentPositionDescription = new Label(dataDescription, SWT.NONE);
		currentPositionDescription.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		currentPositionDescription.setToolTipText(VALUE_CHANGE_TOOLTIP);
		
		canvas = new Canvas(dataGroup, SWT.NONE);
		canvas.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
		canvas.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
		canvas.setMenu(styleMenu);
		canvas.addMouseMoveListener(new MouseMoveListener() {

			
			public void mouseMove(MouseEvent e) {
				currentCursorColumn = getColumnNumber(e);
				updateCursorLabel();
			}
			
		});
		canvas.addMouseListener(new MouseListener() {

			
			public void mouseDoubleClick(MouseEvent e) {
				
			}

			
			public void mouseDown(MouseEvent e) {
				
			}

			
			public void mouseUp(MouseEvent e) {
				if (e.button != 1) return; // left button only
				int column = getColumnNumber(e);
				if (column != -1) {
					doCursor(column);
				}	
			}
			
		});
		
		canvas.addListener(SWT.Paint, new Listener () {
			public void handleEvent(Event event) {
				GC gc = event.gc;
				drawGraph(gc, event.width, event.height);
			}
			
		});
		
		navigationCanvas = new Canvas(dataGroup, SWT.NONE);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.heightHint = 45;
		navigationCanvas.setLayoutData(gd);
		navigationCanvas.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_GRAY)); 
		
		navigationCanvas.addPaintListener(new PaintListener() {

			public void paintControl(PaintEvent event) {
				GC gc = event.gc;
				drawNavigator(gc, event.width, event.height);
			}
			
		});
		
		navigationCanvas.addMouseListener(new MouseListener() {

			public void mouseDoubleClick(MouseEvent e) {
			}

			public void mouseDown(MouseEvent e) {
			}

			public void mouseUp(MouseEvent e) {
				if (e.button == 1) {
					int max = result.getSamplesCount() - samplesPerPage;
					int col = getNavCol(e);
					samplesOffset = col > max ? max : col;
					updatePosition();
					updateZoom();
				} else if (e.button == 3) {
					int col = getNavCol(e);
					
					int diffC1 = cursor1Position - col;
					int diffC2 = cursor2Position - col;
					
					if (diffC1 < 1 && diffC1 > -1) {
						doCursor(cursor1Position);
					} else if (diffC2 < 1 && diffC2 > -1) {
						doCursor(cursor2Position);
					} else {
						doCursor(col);
					}
					
					doCursorValues();
					canvas.redraw();
					navigationCanvas.redraw();
				}
			}
			
		});
		
		Composite middleNavigation = new Composite(dataGroup, SWT.NONE);
		middleNavigation.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		middleNavigation.setLayout(new GridLayout(9, false));
		
		zoomIn = new Button(middleNavigation, SWT.PUSH);
		zoomIn.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		zoomIn.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.ZOOM_IN));
		zoomIn.setToolTipText("Zoom In");
		zoomIn.addSelectionListener(new SelectionListener() {

			
			public void widgetDefaultSelected(SelectionEvent e) {
			}

			
			public void widgetSelected(SelectionEvent e) {
				samplesPerPage -= 10;
				if (samplesPerPage < minZoom) {
					samplesPerPage = minZoom;
				}
				updatePosition();
				updateZoom();
			}
			
		});
		
		zoomFit = new Button(middleNavigation, SWT.PUSH);
		zoomFit.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		zoomFit.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.ZOOM_FIT));
		zoomFit.setToolTipText("Zoom to fit area between cursors");
		zoomFit.addSelectionListener(new SelectionListener() {

			
			public void widgetDefaultSelected(SelectionEvent e) {
			}

			
			public void widgetSelected(SelectionEvent e) {
				int distance = Math.abs(cursor1Position - cursor2Position) + 1;
				if (distance >= minZoom && distance <= maxZoom) {
					samplesPerPage = distance;
				} else if (distance < minZoom) {
					samplesPerPage = minZoom;
				} else if (distance > maxZoom) {
					samplesPerPage = maxZoom;
				}
				
				int maxOffset = result.getSamplesCount() - samplesPerPage;
				samplesOffset = cursor1Position > maxOffset ? maxOffset : cursor1Position;
				updateZoom();
			}
			
		});
		
		zoomOut = new Button(middleNavigation, SWT.PUSH);
		zoomOut.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
		zoomOut.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.ZOOM_OUT));
		zoomOut.setToolTipText("Zoom Out");
		zoomOut.addSelectionListener(new SelectionListener() {

			
			public void widgetDefaultSelected(SelectionEvent e) {
			}

			
			public void widgetSelected(SelectionEvent e) {
				samplesPerPage += 10;
				int max = maxZoom > result.getSamplesCount() ? result.getSamplesCount() : maxZoom;
				if (max + samplesOffset > result.getSamplesCount()) {
					max = result.getSamplesCount() - samplesOffset;
				}
				if (samplesPerPage > max) {
					samplesPerPage = max;
				}
				updatePosition();
				updateZoom();
			}
			
		});
		
		discardCursors = new Button(middleNavigation, SWT.PUSH);
		discardCursors.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, true, false));
		discardCursors.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.DISCARD_CURSORS));
		discardCursors.setEnabled(false);
		discardCursors.setToolTipText("Discard cursors");
		discardCursors.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
			}

			public void widgetSelected(SelectionEvent e) {
				cursor1Position = -1;
				cursor2Position = -1;
				canvas.redraw();
				navigationCanvas.redraw();
				doCursorValues();
				zoomFit.setEnabled(false);
				discardCursors.setEnabled(false);
			}
			
		});
		
		
		
		goFirst = new Button(middleNavigation, SWT.PUSH);
		goFirst.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
		goFirst.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.NAVIGATION_GO_FIRST));
		goFirst.setToolTipText("Jump to the begining");
		goFirst.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
			}

			public void widgetSelected(SelectionEvent e) {
				samplesOffset = 0;
				updatePosition();
				updateZoom();
			}
			
		});
		
		goPrevious = new Button(middleNavigation, SWT.PUSH);
		goPrevious.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
		goPrevious.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.NAVIGATION_GO_PREVIOUS));
		goPrevious.addSelectionListener(new SelectionListener() {

			public void widgetDefaultSelected(SelectionEvent e) {
			}

			
			public void widgetSelected(SelectionEvent e) {
				samplesOffset -= jumpSize;
				if (samplesOffset < 0) {
					samplesOffset = 0;
				}
				updatePosition();
				updateZoom();
			}
			
		});
		
		
		goJump = new Button(middleNavigation, SWT.PUSH);
		goJump.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
		goJump.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.NAVIGATION_GO_JUMP));
		goJump.setToolTipText("Select a sample to jump to");
		goJump.addSelectionListener(new SelectionListener() {

			
			public void widgetDefaultSelected(SelectionEvent e) {
			}

			
			public void widgetSelected(SelectionEvent e) {
				final int from = 0;
				final int to = result.getSamplesCount() - samplesPerPage;
				IInputValidator validator = new IInputValidator() {

					
					public String isValid(String newText) {
						int input;
						try {
							input = Integer.parseInt(newText);
						} catch (NumberFormatException e) {
							return "Input value has to be in range between " + from + " and " + to  + ".";
						}
						if (input < from) {
							return "Input value has to be greater than or equal to " + from + ".";
						} else if (input > to) {
							return "Input value has to be less than or equal to " + to + ".";
						}
						return null;
					}
					
				};
				String dialogMessage = "Insert number of sample you want to go to.\nIt has to be number between " + from + " and " + to  + ".";
				InputDialog inputDialog = new InputDialog(composite.getShell(), "Go to...", dialogMessage, String.valueOf(to / 2), validator);
				int returnCode = inputDialog.open();
				if (returnCode == SWT.CANCEL) return;
				String stringValue = inputDialog.getValue();
				int value;
				try {
					value = Integer.parseInt(stringValue);
				} catch (NumberFormatException ee) {
					return;
				}
				samplesOffset = value;
				updatePosition();
				updateZoom();
			}
			
		});
		
		goNext = new Button(middleNavigation, SWT.PUSH);
		goNext.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
		goNext.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.NAVIGATION_GO_NEXT));
		goNext.addSelectionListener(new SelectionListener() {

			
			public void widgetDefaultSelected(SelectionEvent e) {
			}

			
			public void widgetSelected(SelectionEvent e) {
				samplesOffset += jumpSize;
				if (samplesOffset > result.getSamplesCount() - samplesPerPage) {
					samplesOffset = result.getSamplesCount() - samplesPerPage;
				}
				updatePosition();
				updateZoom();
			}
			
		});
		
		goLast = new Button(middleNavigation, SWT.PUSH);
		goLast.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
		goLast.setImage(GraphEditorPlugin.getDefault().getImageRegistry().get(GraphEditorPlugin.NAVIGATION_GO_LAST));
		goLast.setToolTipText("Jump to the end");
		goLast.addSelectionListener(new SelectionListener() {

			
			public void widgetDefaultSelected(SelectionEvent e) {
			}

			
			public void widgetSelected(SelectionEvent e) {
				samplesOffset = result.getSamplesCount() - samplesPerPage;
				updatePosition();
				updateZoom();
			}
			
		});
		
		// bottom
		Composite measurings = new Composite(composite, SWT.NONE);
		measurings.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		measurings.setLayout(new GridLayout(1, false));
		
		Group measuringsGroup = new Group(measurings, SWT.NONE);
		measuringsGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
		measuringsGroup.setLayout(new GridLayout(3, false));
		measuringsGroup.setText("Measurings");
		
		// bottom left
		Composite bottomLeft = new Composite(measuringsGroup, SWT.NONE);
		bottomLeft.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, true));
		bottomLeft.setLayout(new GridLayout(2, false));
		
		label = new Label(bottomLeft, SWT.NONE);
		gd = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
		gd.horizontalSpan = 2;
		label.setLayoutData(gd);
		label.setText("Cursor 1");
		
		label = new Label(bottomLeft, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Position:");
		
		c1Position = new Text(bottomLeft, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 120;
		c1Position.setLayoutData(gd);
		c1Position.setEnabled(false);
		c1Position.setToolTipText(VALUE_CHANGE_TOOLTIP);
		
		label = new Label(bottomLeft, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Value:");
		
		c1Value = new Text(bottomLeft, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 120;
		c1Value.setLayoutData(gd);
		c1Value.setEnabled(false);
		c1Value.setToolTipText(VALUE_CHANGE_TOOLTIP);
		
		// bottom middle
		Composite bottomMiddle = new Composite(measuringsGroup, SWT.NONE);
		bottomMiddle.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, true));
		bottomMiddle.setLayout(new GridLayout(2, false));
		
		label = new Label(bottomMiddle, SWT.NONE);
		gd = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
		gd.horizontalSpan = 2;
		label.setLayoutData(gd);
		label.setText("Cursor 2");
		
		label = new Label(bottomMiddle, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Position:");
		
		c2Position = new Text(bottomMiddle, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 120;
		c2Position.setLayoutData(gd);
		c2Position.setEnabled(false);
		c2Position.setToolTipText(VALUE_CHANGE_TOOLTIP);
		
		label = new Label(bottomMiddle, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Value:");
		
		c2Value = new Text(bottomMiddle, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 120;
		c2Value.setLayoutData(gd);
		c2Value.setEnabled(false);
		c2Value.setToolTipText(VALUE_CHANGE_TOOLTIP);
		
		// bottom right
		Composite bottomRight = new Composite(measuringsGroup, SWT.NONE);
		bottomRight.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, true));
		bottomRight.setLayout(new GridLayout(2, false));
		
		label = new Label(bottomRight, SWT.NONE);
		gd = new GridData(SWT.BEGINNING, SWT.CENTER, false, false);
		gd.horizontalSpan = 2;
		label.setLayoutData(gd);
		label.setText("Statistics");
		
		label = new Label(bottomRight, SWT.NONE);
		label.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));
		label.setText("Distance:");
		
		statisticsDistance = new Text(bottomRight, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
		gd = new GridData(SWT.FILL, SWT.CENTER, true, false);
		gd.widthHint = 120;
		statisticsDistance.setLayoutData(gd);
		statisticsDistance.setEnabled(false);
		statisticsDistance.setToolTipText(VALUE_CHANGE_TOOLTIP);
		
		setupValues(result);
	}

	private int getNavCol(MouseEvent e) {
		double relativePosition = (double) e.x / navigationCanvas.getSize().x;
		int col = (int) (result.getSamplesCount() * relativePosition);
		return col;
	}


	private void drawNavigator(GC gc, int width, int height) {
		int sampleCount = width / NAVIGATOR_COL_WIDTH;
		int pointArray[] = new int[sampleCount * 4];
		int yLow = (int) (height * 0.2);
		int yHigh = (int) (height * 0.8);
		for (int i = 0; i < sampleCount; i++) {
			int x1 = (int) (NAVIGATOR_COL_WIDTH * 0.5 + NAVIGATOR_COL_WIDTH * (i));
			int x2 = (int) (NAVIGATOR_COL_WIDTH * 0.5 + NAVIGATOR_COL_WIDTH * (i + 1));
			int y = ((i % 2) == 0) ? yLow : yHigh;
			pointArray[i*4] = x1;
			pointArray[i*4 + 1] = y;
			pointArray[i*4 + 2] = x2;
			pointArray[i*4 + 3] = y;
		}
		gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
		gc.drawPolyline(pointArray);
		
		drawNavPosition(gc, width, height, samplesOffset, SWT.COLOR_RED);
		drawNavPosition(gc, width, height, samplesOffset + samplesPerPage - 1, SWT.COLOR_RED);
		
		if (cursor1Position > -1) {
			int colorNum = getCursorColorNum(cursor1Position);
			drawNavPosition(gc, width, height, cursor1Position, colorNum);
		}
		if (cursor2Position > -1) {
			int colorNum = getCursorColorNum(cursor2Position);
			drawNavPosition(gc, width, height, cursor2Position, colorNum);
		}
	}
	
	private int getCursorColorNum(int cursorPos) {
		int colorNumber = SWT.COLOR_BLUE;
		if (cursorPos == samplesOffset || cursorPos == samplesOffset + samplesPerPage - 1) {
			colorNumber = SWT.COLOR_BLACK;
		}
		return colorNumber;
	}


	private void drawNavPosition(GC gc, int width, int height, int sampleNumber, int systemColorNumber) {
		int x = getNavObjectXPosition(width, sampleNumber);
		gc.setForeground(Display.getDefault().getSystemColor(systemColorNumber));
		gc.setLineWidth(2);
		gc.drawLine(x, height, x, 0);
		gc.setLineWidth(1);
	}


	private int getNavObjectXPosition(int pxWidth, int sampleNumber) {
		double relativePosition = (double) sampleNumber / result.getSamplesCount();
		int result = (int) (pxWidth * relativePosition);
		return result + 1; // + 1 to be able to show line of width 2
	}

	private void createPopupMenu(Composite parent) {
		styleMenu = new Menu(parent);

		final MenuItem timeUnitsItem = new MenuItem(styleMenu, SWT.CASCADE);
		timeUnitsItem.setText("Time units");
		final MenuItem valueFormItem = new MenuItem(styleMenu, SWT.CASCADE);
		valueFormItem.setText("Value form");
		
		TimeUnitsSelectionListener timeUnitsSL = new TimeUnitsSelectionListener();
		
		Menu timeUnitsSubmenu = new Menu(timeUnitsItem);
		timeUnitsS = new MenuItem(timeUnitsSubmenu, SWT.RADIO);
		timeUnitsS.setText("s");
		timeUnitsS.addSelectionListener(timeUnitsSL);
		timeUnitsMs = new MenuItem(timeUnitsSubmenu, SWT.RADIO);
		timeUnitsMs.setText("ms");
		timeUnitsMs.addSelectionListener(timeUnitsSL);
		timeUnitsNs = new MenuItem(timeUnitsSubmenu, SWT.RADIO);
		timeUnitsNs.setText("\u00B5s");
		timeUnitsNs.addSelectionListener(timeUnitsSL);
		timeUnitsItem.setMenu(timeUnitsSubmenu);
		
		ValueFormSelectionListener valueFormSL = new ValueFormSelectionListener();
		
		Menu valueFormsSubmenu = new Menu(valueFormItem);
		valueFormsDec = new MenuItem(valueFormsSubmenu, SWT.RADIO);
		valueFormsDec.setText("Decimal");
		valueFormsDec.addSelectionListener(valueFormSL);
		valueFormsHex = new MenuItem(valueFormsSubmenu, SWT.RADIO);
		valueFormsHex.setText("Hex");
		valueFormsHex.addSelectionListener(valueFormSL);
		valueFormsBin = new MenuItem(valueFormsSubmenu, SWT.RADIO);
		valueFormsBin.setText("Binary");
		valueFormsBin.addSelectionListener(valueFormSL);
		valueFormItem.setMenu(valueFormsSubmenu);
		
	}
	
	private class TimeUnitsSelectionListener implements SelectionListener {

		
		public void widgetDefaultSelected(SelectionEvent e) {
			widgetSelected(e);
		}

		
		public void widgetSelected(SelectionEvent e) {
			if (e.widget instanceof MenuItem) {
				if (!((MenuItem) e.widget).getSelection()) return;
			} else {
				return;
			}
			
			if (e.widget == timeUnitsS) {
				timeStyle = MetadataProvider.SECOND;
			} else if (e.widget == timeUnitsMs) {
				timeStyle = MetadataProvider.MILISECOND;
			} else if (e.widget == timeUnitsNs) {
				timeStyle = MetadataProvider.MICROSECOND;
			}
			refreshMetadata();
		}
		
	}
	
	private class ValueFormSelectionListener implements SelectionListener {

		
		public void widgetDefaultSelected(SelectionEvent e) {
			widgetSelected(e);
		}

		
		public void widgetSelected(SelectionEvent e) {
			if (e.widget instanceof MenuItem) {
				if (!((MenuItem) e.widget).getSelection()) return;
			} else {
				return;
			}
			
			if (e.widget == valueFormsDec) {
				valueStyle = MetadataProvider.DECIMAL;
			} else if (e.widget == valueFormsHex) {
				valueStyle = MetadataProvider.HEX;
			} else if (e.widget == valueFormsBin) {
				valueStyle = MetadataProvider.BINARY;
			}
			refreshMetadata();
		}
		
	}

	private void updateZoom() {
		setDataSummary();
		canvas.redraw();
		navigationCanvas.redraw();
		int max = maxZoom > result.getSamplesCount() ? result.getSamplesCount() : maxZoom;
		if (max + samplesOffset > result.getSamplesCount()) {
			max = result.getSamplesCount() - samplesOffset;
		}
		boolean isMinZoom = samplesPerPage == minZoom;
		boolean isMaxZoom = samplesPerPage == max;
		boolean doGoJump = result.getSamplesCount() - samplesPerPage > 2;
		zoomIn.setEnabled(!isMinZoom);
		zoomOut.setEnabled(!isMaxZoom);
		goJump.setEnabled(doGoJump);
		updateMoveButtons();
		setFocus();
	}

	private void refreshMetadata() {
		metadataProvider = new MetadataProvider(result, valueStyle, timeStyle);
		setDataSummary();
		doCursorValues();
		updateCursorLabel();
	}

	private void updatePosition() {
		setDataSummary();
		canvas.redraw();
		navigationCanvas.redraw();
		updateMoveButtons();
		setFocus();
	}

	private void updateMoveButtons() {
		boolean atTheBegining = samplesOffset == 0;
		goFirst.setEnabled(!atTheBegining);
		goPrevious.setEnabled(!atTheBegining);
		boolean atTheEnd = samplesOffset == result.getSamplesCount() - samplesPerPage;
		goNext.setEnabled(!atTheEnd);
		goLast.setEnabled(!atTheEnd);
	}

	private void updateCursorLabel() {
		if (currentCursorColumn == -1) {
			currentPositionDescription.setText("Current position: Not relevant");
		} else {
			int position = currentCursorColumn;
			currentPositionDescription.setText("Current position: Sample " + metadataProvider.getSampleDesc(position) + 
					", value " + metadataProvider.getValueWithUnits(position));
		}
		currentPositionDescription.pack();
		
	}

	private void doCursor(int position) {
		if (cursor1Position == -1 && cursor2Position != position) {
			cursor1Position = position;
		} else if (cursor2Position == -1 && cursor1Position != position) {
			cursor2Position = position;
		} else {
			if (cursor1Position == position) {
				cursor1Position = -1;
			} else if (cursor2Position == position) {
				cursor2Position = -1;
			}
		}
		
		if (cursor1Position > cursor2Position && cursor2Position != -1) { // swap cursors to make cursor1 to be always on left
			int temp = cursor1Position;
			cursor1Position = cursor2Position;
			cursor2Position = temp;
		}
		
		canvas.redraw();
		navigationCanvas.redraw();
		doCursorValues();
		zoomFit.setEnabled(cursor1Position != -1 && cursor2Position != -1);
		discardCursors.setEnabled(cursor1Position != -1 || cursor2Position != -1);
	}

	private void doCursorValues() {
		if (cursor1Position == -1) {
			c1Position.setText("");
			c1Value.setText("");
		} else {
			c1Position.setText(metadataProvider.getSampleDesc(cursor1Position));
			c1Value.setText(metadataProvider.getValueWithUnits(cursor1Position));
		}
		
		if (cursor2Position == -1) {
			c2Position.setText("");
			c2Value.setText("");
		} else {
			c2Position.setText(metadataProvider.getSampleDesc(cursor2Position));
			c2Value.setText(metadataProvider.getValueWithUnits(cursor2Position));
		}
		
		if (cursor1Position != -1 && cursor2Position != -1) {
			int distance = Math.abs(cursor1Position - cursor2Position);
			statisticsDistance.setText(metadataProvider.getSampleRangeDesc(distance));
		} else {
			statisticsDistance.setText("");
		}
	}

	
	public void setFocus() {
		canvas.setFocus();
	}
	
	private void drawGraph(GC gc, int width, int height) {
		Integer[] validSignals = getValidSignals(result.getResultMask());
		int signalCount = validSignals.length;
		for (int i = 0; i < signalCount; i++) {
			drawOneSignal(gc, 0, (height / (signalCount + 1)) * i, width, height / (signalCount + 1), validSignals[i]);
		}
		drawVerticals(gc, 0, 0, width, height); // TODO begin of the vertical should be configurable or driven by text
	}
	
	private Integer[] getValidSignals(int resultMask) {
		List<Integer> result = new ArrayList<Integer>();
		for (int i = 0; i < 8; i++) {
			int mask = (int) Math.pow(2, i);
			if ((resultMask & mask) > 0) {
				result.add(i);
			}
		}
		return result.toArray(new Integer[0]);
	}

	private void drawVerticals(GC gc, int posX, int posY, int width, int height) {
		int xWithOffset = posX + VERTICAL_OFFSET;
		gc.setLineWidth(3);
		gc.drawLine(xWithOffset, 0, xWithOffset, height);
		gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY));
		gc.setLineWidth(1);
		int widthPerColumn = (int) ((width - VERTICAL_OFFSET) / (samplesPerPage + 0.5));
		for (int i = 0; i < samplesPerPage; i++) {
			int x = (int) (xWithOffset + widthPerColumn * 0.5 + widthPerColumn * (i + 0.5));
			if (i == cursor1Position - samplesOffset || i == cursor2Position - samplesOffset) {
				gc.setLineStyle(SWT.LINE_SOLID);
				gc.setLineWidth(3);
				gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLUE));
				gc.drawLine(x, 0, x, (int) (height / 9 * 8.3));
				gc.setLineWidth(1);
				gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_GRAY));
			} else {
				gc.setLineStyle(SWT.LINE_DOT);
				gc.drawLine(x, 0, x, (int) (height / 9 * 8.3));
			}	
		}
	}
	
	private void drawOneSignal(GC gc, int posX, int posY, int width, int height, int order) {
		int xWithOffset = posX + VERTICAL_OFFSET;
		int widthPerColumn = (int) ((width - VERTICAL_OFFSET) / (samplesPerPage + 0.5));
		gc.setFont(new Font(gc.getDevice(), "Arial", 10, SWT.NONE));
		gc.drawText(result.getPinNames().get(order), posX + 5, (posY + height / 2) - 5);
		gc.drawLine(posX, posY + height, posX + width, posY + height);
		int yHigh = (int) (posY + height * 0.2);
		int yLow = (int) (posY + height * 0.8);
		int pointArray[] = new int[samplesPerPage * 4];
		for (int i = 0; i < samplesPerPage; i++) {
			int x1 = (int) (xWithOffset + widthPerColumn * 0.5 + widthPerColumn * (i));
			int x2 = (int) (xWithOffset + widthPerColumn * 0.5 + widthPerColumn * (i + 1));
			int y = getSingleSignalValue(samplesOffset + i, order) ? yHigh : yLow;
			pointArray[i*4] = x1;
			pointArray[i*4 + 1] = y;
			pointArray[i*4 + 2] = x2;
			pointArray[i*4 + 3] = y;
		}
		gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
		gc.drawPolyline(pointArray);
		gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
	}
	
	private boolean getSingleSignalValue(int position, int bit) {
		int byteValue = result.getContentAtPosition(position);
		int mask = (int) Math.pow(2, bit);
		int result = byteValue & mask;
		return result != 0;
	}
	
	/*private boolean getSingleSignalValue(int position, int bit) {
		return ((position / (bit + 1)) % 6) == 0 || ((position / (bit + 1)) % 7) == 0;
	}*/
	
	private void setupValues(IResult result) {
		IPreferenceStore prefStore = GraphEditorPlugin.getDefault().getPreferenceStore();
		date.setText(dateFormat.format(result.getDateOfCreation()));
		int addresValue = result.getAddress();
		String addresText = "0x" + Integer.toHexString(addresValue) + " (" + String.valueOf(addresValue) + ")";
		address.setText(addresText);
		desiredFreq.setText(String.valueOf(result.getDesiredFrequency()) + " " + result.getDesiredFrequencyUnits());
		
		float realFrequency = result.getRealFrequency();
		String realFrequencyUnits = "Hz";
		if (realFrequency > 999) {
			realFrequency = Math.round((realFrequency / 1000) * 100);
			realFrequency /= 100;
			realFrequencyUnits = "kHz";
		}	
		String formattedFrequency = NumberFormat.getInstance().format(realFrequency);
		realFreq.setText(formattedFrequency + " " + realFrequencyUnits);
		TriggerDescription triggerDescription = result.getTriggerDescription();
		boolean triggerDescPresented = triggerDescription != null;
		if (triggerDescPresented) {
			CustomizedPin pin = triggerDescription.getPin();
			triggerPinDescription.setText(pin.getCustomizedLabel());
			String risingEdgeDesc = triggerDescription.isRisingEdge() ? "Rising Edge" : "Falling Edge";
			triggerEdgeDescription.setText(risingEdgeDesc);
		} else {
			triggerPinDescription.setText("Not Used");
		}
		
		valueStyle = Integer.parseInt(prefStore.getString(PreferenceConstants.DEFAULT_VALUE_STYLE));
		timeStyle = Integer.parseInt(prefStore.getString(PreferenceConstants.DEFAULT_TIME_STYLE));
		metadataProvider = new MetadataProvider(result, valueStyle, timeStyle);
		samplesOffset = 0;
		
		int defaultSamplesPerPage = prefStore.getInt(PreferenceConstants.DEFAULT_SAMPLES_PER_PAGE);
		samplesPerPage = defaultSamplesPerPage > result.getSamplesCount() ? result.getSamplesCount() : defaultSamplesPerPage;
		
		jumpSize = prefStore.getInt(PreferenceConstants.JUMP_SIZE);
		minZoom = prefStore.getInt(PreferenceConstants.MIN_ZOOM);
		maxZoom = prefStore.getInt(PreferenceConstants.MAX_ZOOM);
		
		goNext.setToolTipText("Go " + jumpSize + " samples forward");
		goPrevious.setToolTipText("Go " + jumpSize + " samples backward");
		
		checkDefaultStyleButtons();
		
		updatePosition();
		updateZoom();
		currentPositionDescription.setText("Current position: Not relevant");
		zoomFit.setEnabled(false);
	}
	
	private void checkDefaultStyleButtons() {
		switch (timeStyle) {
		case MetadataProvider.SECOND:
			timeUnitsS.setSelection(true);
			timeUnitsNs.setSelection(false);
			timeUnitsMs.setSelection(false);
			break;
		case MetadataProvider.MILISECOND:
			timeUnitsS.setSelection(false);
			timeUnitsNs.setSelection(false);
			timeUnitsMs.setSelection(true);
			break;
		case MetadataProvider.MICROSECOND:
			timeUnitsS.setSelection(false);
			timeUnitsNs.setSelection(true);
			timeUnitsMs.setSelection(false);
			break;
		}
		
		switch (valueStyle) {
		case MetadataProvider.DECIMAL:
			valueFormsBin.setSelection(false);
			valueFormsDec.setSelection(true);
			valueFormsHex.setSelection(false);
			break;
		case MetadataProvider.HEX:
			valueFormsBin.setSelection(false);
			valueFormsDec.setSelection(false);
			valueFormsHex.setSelection(true);
			break;
		case MetadataProvider.BINARY:
			valueFormsBin.setSelection(true);
			valueFormsDec.setSelection(false);
			valueFormsHex.setSelection(false);
			break;
		}
	}

	private void setDataSummary() {
		dataSummary.setText("First sample: " + metadataProvider.getSampleDesc(samplesOffset) + ", Displayed samples: " + 
				metadataProvider.getSampleRangeDesc(samplesPerPage) + ", Total count: " + 
				metadataProvider.getSampleRangeDesc(result.getSamplesCount()));
		dataSummary.pack();
	}

	private int getColumnNumber(MouseEvent e) {
		Canvas canvas = (Canvas) e.widget;
		int widthPerColumn = (int) ((canvas.getSize().x - VERTICAL_OFFSET) / (samplesPerPage + 0.5));
		int x = e.x - (int) (VERTICAL_OFFSET);
		if (x < widthPerColumn / 2) return -1;
		int i = x / widthPerColumn;
		int mod = x - (widthPerColumn * i);
		double toleranceCoeff = 0.3;
		if (mod <= widthPerColumn * toleranceCoeff) {
			int value = i - 1;
			if (value > samplesPerPage - 1) return -1; // prevents running out of displayed samples
			return value + samplesOffset;
		} else if (mod >= widthPerColumn * (1 - toleranceCoeff)) {
			if (i > samplesPerPage - 1) return -1; // prevents running out of displayed samples
			return i + samplesOffset;
		}
		return -1;
	}

}


/*
 * $Log$
 */